[毎日Kotlin] Day36. Delegates(委譲)
はじめに
毎日Kotlinシリーズです。
このシリーズを初めての方はこちらです。「毎日Kotlin」はじめました | Developers.IO
問題
Delegates how it works | Try Kotlin
You may declare your own delegates. Implement the methods of the class 'EffectiveDate' so it can be delegated to. Store only the time in milliseconds in 'timeInMillis' property.
Use the extension functions MyDate.toMillis() and Long.toDate(), defined at MyDate.kt
import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty class D { var date: MyDate by EffectiveDate() } class EffectiveDate<R> : ReadWriteProperty<R, MyDate> { var timeInMillis: Long? = null override fun getValue(thisRef: R, property: KProperty<*>): MyDate { TODO() } override fun setValue(thisRef: R, property: KProperty<*>, value: MyDate) { TODO() } }
import java.util.Calendar data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) fun MyDate.toMillis(): Long { val c = Calendar.getInstance() c.set(year, month, dayOfMonth, 0, 0, 0) c.set(Calendar.MILLISECOND, 0) return c.getTimeInMillis() } fun Long.toDate(): MyDate { val c = Calendar.getInstance() c.setTimeInMillis(this) return MyDate(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DATE)) }
狙い
ここで考えて欲しい問題の意図はなんだろうか?
ライブラリーなどを作るときに重宝します。Javaではなかった機能なので何度か練習は必要です。
解答例
class EffectiveDate<R> : ReadWriteProperty<R, MyDate> { var timeInMillis: Long? = null override fun getValue(thisRef: R, property: KProperty<*>): MyDate { return timeInMillis!!.toDate() } override fun setValue(thisRef: R, property: KProperty<*>, value: MyDate) { timeInMillis = value.toMillis() } }
簡単に言うと、getterとsetterのかわりを行うクラスを作り、それに委譲するイメージです。
たとえば、スタンダードライブラリーではDelegatesのnotNullの定義をみてみましょう。
Delegates.kt at 1.2.30 · JetBrains/kotlin
private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> { private var value: T? = null public override fun getValue(thisRef: Any?, property: KProperty<*>): T { return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.") } public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { this.value = value } }
ReadWriteProperty<Any?, T>の部分ですが、Any?型で定義できるプロパティを限定しています。この場合は、Any?なのですべてのクラスでこの委譲プロパティがつかえます。たとえば、Hogeクラだけで使用する場合は、ReadWriteProperty<Hoge, T>にします。
2つ目のTは、setterで入力される型と同時にgetterで返される型を指定します。当然といえば当然ですが、setterでいれる型もgetterで取得できる型も同じですね。notNullの場合は、どのクラスでも使用できるようにTにして、宣言時に決めます。常にStringだけ返すような場合は、ReadWriteProperty<Any?, String>にします。
すごいシンプルな例を、みんな大好きHello , worldでやってみます。
class Hello { var string by WorldDelegate() } class WorldDelegate : ReadWriteProperty<Hello, String> { var v: String = "" override fun getValue(thisRef: Hello, property: KProperty<*>): String { return v } override fun setValue(thisRef: Hello, property: KProperty<*>, value: String) { v = value + " , world" } } class DelegateTest { @Test fun test() { val hoge = Hello() hoge.string = "hello" Assert.assertEquals("hello , world", hoge.string) } }
ReadWritePropertyはvarですが、valのときはReadOnlyPropertyを使えば、getterだけの定義をすればよいです。
operatorを使った定義
operatorを使って、同様に定義することができます。
class WorldDelegate : ReadWriteProperty<Hello, String> { var v: String = "" override fun getValue(thisRef: Hello, property: KProperty<*>): String { return v } override fun setValue(thisRef: Hello, property: KProperty<*>, value: String) { v = value + " , world" } }
class WorldDelegate { var v: String = "" operator fun getValue(thisRef: Hello, property: KProperty<*>): String { return v } operator fun setValue(thisRef: Hello, property: KProperty<*>, value: String) { v = value + " , world" } }
Javaに戻してみる
インスタンスをつくって、getter,setterをそのインスタンスのgetter,setterに流してるだけなんですけどね。
public final class Hello { // $FF: synthetic field static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Hello.class), "string", "getString()Ljava/lang/String;"))}; @NotNull private final WorldDelegate string$delegate = new WorldDelegate(); @NotNull public final String getString() { return this.string$delegate.getValue(this, $$delegatedProperties[0]); } public final void setString(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.string$delegate.setValue(this, $$delegatedProperties[0], var1); } }
あとがき
Day37.でまたお会いしましょう。